package com.lzj.system.service;

import cn.hutool.core.util.StrUtil;
import com.lzj.common.constant.CacheConstants;
import com.lzj.common.constant.Constants;
import com.lzj.common.core.domain.entity.SysUser;
import com.lzj.common.core.domain.event.LogininforEvent;
import com.lzj.common.core.domain.model.LoginBody;
import com.lzj.common.core.domain.model.LoginUser;
import com.lzj.common.exception.user.CaptchaException;
import com.lzj.common.exception.user.CaptchaExpireException;
import com.lzj.common.exception.user.UserPasswordNotMatchException;
import com.lzj.common.helper.SecurityHelper;
import com.lzj.common.security.service.TokenService;
import com.lzj.common.utils.DateUtils;
import com.lzj.common.utils.MessageUtils;
import com.lzj.common.utils.ServletUtils;
import com.lzj.common.utils.redis.RedisUtils;
import com.lzj.common.utils.spring.SpringUtils;
import com.lzj.system.mapper.SysUserMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;

/**
 * 登录服务
 *
 * @author 贱贱
 */
@RequiredArgsConstructor
@Slf4j
@Service
public class SysLoginService {


    private final AuthenticationManager authenticationManager;

    private final TokenService tokenService;

    private final ISysConfigService configService;
    
    private  final SysUserMapper userMapper;
    

    /**
     * 用户登录
     * @param loginBody 用户登录表单
     * @return 用户登录token
     */
    public String login(LoginBody loginBody) {

        // 检验验证码
        boolean captchaEnabled = configService.selectCaptchaEnabled();
        if (captchaEnabled) {
            validateCaptcha(loginBody.getCode(), loginBody.getUuid());
        }

        // TODO 前置校验 比如IP过滤
        // 用户验证,使用authenticationManager
        Authentication authentication;
        try
        {
            //将用户名密码存入登录信息上下文,后面要在验证密码的时候用到
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginBody.getUsername(), loginBody.getPassword());
            SecurityHelper.setAuthentication(authenticationToken);
            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            authentication = authenticationManager.authenticate(authenticationToken);
        }
        catch (BadCredentialsException e) {
            log.error("用户登录失败",e);
            throw new UserPasswordNotMatchException();
        } finally {
            SecurityHelper.clearAuthentication();
        }
        // 生成token
        LoginUser user = (LoginUser) authentication.getPrincipal();
        // 记录登录信息
        recordLoginInfo(user.getUserId(), user.getUsername());
        recordLoginInfoLog(user.getUsername(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));

        return tokenService.createToken(user);
    }


    /**
     * 校验验证码
     */
    private void validateCaptcha(String code, String uuid) {

        // 从缓存中获取
        String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StrUtil.nullToDefault(uuid, "");
        String captcha = RedisUtils.getCacheObject(verifyKey);
        RedisUtils.deleteObject(verifyKey);
        if (captcha == null) {
            throw new CaptchaExpireException();
        }
        else if (!captcha.equalsIgnoreCase(code)){
            throw new CaptchaException();
        }
    }


    /**
     * 修改用户登录信息
     *
     * @param userId 用户ID
     */
    public void recordLoginInfo(Long userId, String username) {
        SysUser sysUser = new SysUser();
        sysUser.setUserId(userId);
        sysUser.setLoginIp(ServletUtils.getClientIP());
        sysUser.setLoginDate(DateUtils.getNowDate());
        sysUser.setUpdateBy(username);
        userMapper.updateById(sysUser);
    }

    /**
     * 记录登录信息
     *
     * @param username 用户名
     * @param status   状态
     * @param message  消息内容
     */
    private void recordLoginInfoLog(String username, String status, String message) {
        LogininforEvent logininforEvent = new LogininforEvent();
        logininforEvent.setUsername(username);
        logininforEvent.setStatus(status);
        logininforEvent.setMessage(message);
        logininforEvent.setRequest(ServletUtils.getRequest());
        SpringUtils.context().publishEvent(logininforEvent);
    }


}
